"""
Author: XingBing
Date: 2024-03-16
Version: 1.0
Description: Brief description of the program.

Copyright (c) 2024 YaleXing. All rights reserved.

This program is licensed under the BSD 3-Clause License. You may not use this file except in compliance with the License.
"""
import threading
import time
import tkinter.simpledialog  # 使用Tkinter前需要先导入
from tkinter import END, messagebox

import pyautogui
import pydirectinput
import keyboard

# import win32con
# import win32api

# import ctypes

import requests

# from utils import *

# from Controller import *

import queue



############################################################################################################
# 全局变量，用于标识线程是否退出
is_exit = True


press_sec = 0.2 #按键按下时长
long_press_sec = 0.8 #长按按下时长
release_sec = 0.2# 按键抬起后等待
# 支持的按键列表
key_list = ('1', '2', '3', '4', '5', 'r', 't', 'x', 'y', 'f', ) # 最普通的，一次执行一次，固定执行较短时间
key_list_can_long_press = ('e', ) # 检测到连续相同字符可以加长按下时间
key_list_longger_press = ('w', 's', 'a', 'd', ) # 检测到连续相同字符可以加长按下时间，与E区分开，让走路时间更长一些
key_list_unusual = ('q', 'z', 'c', 'v', 'h', 'n', 'p', ) # 特殊处理，执行指令和字母不对应

# 创建一个队列
q_keybuffer = queue.Queue() # 类似fifo的数据类型

# 储存连续两次键值
last_key_name = 'w'


############################################################################################################
# B站获取弹幕对象
class Danmu():
    def __init__(self, room_id):
        # 弹幕url
        self.url = 'https://api.live.bilibili.com/xlive/web-room/v1/dM/gethistory'
        # 请求头
        self.headers = {
            'Host': 'api.live.bilibili.com',
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:78.0) Gecko/20100101 Firefox/78.0',
        }
        # 定义POST传递的参数
        self.data = {
            'roomid': room_id,
            'csrf_token': '',
            'csrf': '',
            'visit_id': '',
        }
        # 日志写对象
        self.log_file_write = open('danmu.log', mode='a', encoding='utf-8')
        # 读取日志
        log_file_read = open('danmu.log', mode='r', encoding='utf-8')
        self.log = log_file_read.readlines()

    def get_danmu(self):
        # 暂停0.5防止cpu占用过高
        time.sleep(1)
        # 获取直播间弹幕
        try :
            html = requests.post(url=self.url, headers=self.headers, data=self.data).json()
            # 解析弹幕列表
            for content in html['data']['room']:
                # 获取昵称
                nickname = content['nickname']
                # 获取发言
                text = content['text']
                # 获取发言时间 HH:mm:ss
                timeline = content['timeline'].split(" ")[1]
                # 记录发言
                msg = timeline + ' ' + nickname + ': ' + text
                # 判断对应消息是否存在于日志，如果和最后一条相同则打印并保存
                if msg + '\n' not in self.log:
                    # 打印消息
                    listb.insert(END, msg)
                    listb.see(END)

                    # 控制游戏操作
                    # 入队
                    for char in text: # 拆分成单字母后入fifo
                        q_keybuffer.put(char)
                    # q_keybuffer.put(text)
                    # q_keybuffer.put("元素2")
                    # q_keybuffer.put("元素3")

                    # 保存日志
                    self.log_file_write.write(msg + '\n')
                    # 添加到日志列表
                    self.log.append(msg + '\n')
                # 清空变量缓存
                nickname = ''
                text = ''
                timeline = ''
                msg = ''
        except :
            print("获取弹幕失败")
            return



# 线程对象
def bilibili(room_id):
    # 创建bDanmu实例
    bDanmu = Danmu(room_id)
    # 获取弹幕
    bDanmu.get_danmu()


class BilibiliThread(threading.Thread):
    def __init__(self, room_id=None):
        threading.Thread.__init__(self)
        self.room_id = room_id

    # 重写run()方法
    def run(self):
        global is_exit
        while not is_exit:
            bilibili(self.room_id)
            # 暂停防止cpu占用过高
            time.sleep(0.4) # 0.5

class ControllerThread(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)

    # 重写run()方法
    def run(self):
        global is_exit
        while not is_exit:
            control()
            # 暂停防止cpu占用过高
            time.sleep(0.1)

def author():
    # 弹出对话框
    messagebox.showinfo(title='关于', message='作者：川乐水兴\nGitHub：YaleXing\n日期：2023年11月27日first\n版本：0.5')


# 实例化object，建立窗口window
window = tkinter.Tk()
# 给窗口的可视化起名字
window.title('BiliBli弹幕控制游戏')
# 设定窗口的大小(长 * 宽)
window.minsize(300, 110)
window.geometry('400x600+250+100')
# 窗口置顶
window.wm_attributes('-topmost', 1)

# 菜单栏
menubar = tkinter.Menu(window)
# Open放在菜单栏中，就是装入容器
menubar.add_command(label='关于', command=author)
# 创建菜单栏完成后，配置让菜单栏menubar显示出来
window.config(menu=menubar)

# 创建一个主frame，长在主window窗口上
frame = tkinter.Frame(window)
frame.pack()

# 创建第二层框架frame，长在主框架frame上面
# 上
frame_t = tkinter.Frame(frame)
# 下
frame_b = tkinter.Frame(frame)
frame_t.pack(side=tkinter.TOP)
frame_b.pack(side=tkinter.BOTTOM)

# 创建标签
tkinter.Label(frame_t, text='请输入房间号：', width=10, font=('Arial', 10)).pack(side=tkinter.LEFT)
# 显示成明文形式
default_text = tkinter.StringVar()
default_text.set("5363893") # 21089733
e1 = tkinter.Entry(frame_t, show=None, width=15, textvariable=default_text, font=('Arial', 10))
e1.pack(side=tkinter.LEFT)


# 定义两个触发事件时的函数start_point和end_point（注意：因为Python的执行顺序是从上往下，所以函数一定要放在按钮的上面）
# 开始
def start_point():
    try:
        room = e1.get()
        room_int = int(room)
        e1.configure(state=tkinter.DISABLED)
        b1.configure(state=tkinter.DISABLED)
        b2.configure(state=tkinter.NORMAL)
        if room_int is not None:
            global is_exit
            is_exit = False
            t = BilibiliThread()
            t.room_id = room_int
            # 创建获取弹幕线程
            t.setDaemon(True)
            t.start()

            c = ControllerThread()
            # 创建控制操作线程
            c.setDaemon(True)
            c.start()
    except ValueError:
        messagebox.showinfo(title='警告', message='输入的房间号格式不正确,请再次尝试输入!')


# 停止
def end_point():
    global is_exit
    is_exit = True
    e1.configure(state=tkinter.NORMAL)
    b1.configure(state=tkinter.NORMAL)
    b2.configure(state=tkinter.DISABLED)


def click_key(key_name):
    print("发出指令，按下", key_name)
    pydirectinput.keyDown(key_name)

    time.sleep(press_sec)
    pydirectinput.keyUp(key_name)

    print("结束指令，松开", key_name)
    time.sleep(release_sec)

def press_key(key_name):
    print("发出指令，按住", key_name)
    pydirectinput.keyDown(key_name)
    time.sleep(press_sec)

def release_key(key_name):
    print("结束指令，松开", key_name)
    pydirectinput.keyUp(key_name)
    time.sleep(release_sec)


# 控制游戏
def control(): # key_name
    # press_key(87)
    # press_key(16)
    # tap_key(87,0.5)
    # print("W")
    # 出队
    if not q_keybuffer.empty():
        element = q_keybuffer.get()
        print("出队元素:", element)
    else:
        element = None
    global last_key_name
    key_name = element
    # print("key_name =", key_name)
    if key_name == None:
        # print("本次无指令发出")
        if keyboard.is_pressed(last_key_name) :
            release_key(last_key_name)
        return
    key_name = key_name.lower()
    # 控制电脑指令
    if key_name in key_list_unusual:
        if key_name == 'v' : # 奔跑不释放按下的awsd键，只释放e
            print("结束指令，松开e")
            pydirectinput.keyUp('e')
            print("发出指令奔跑")
            # print("按住w")
            # pydirectinput.keyDown('w')
            pydirectinput.keyDown('shift')
            time.sleep(long_press_sec)
            pydirectinput.keyUp('shift')
        else :
            release_key(last_key_name)
            if key_name == 'z' :
                print("发出指令重击")
                pyautogui.mouseDown(button="left")
                time.sleep(0.5) # long_press_sec
                pyautogui.mouseUp(button="left")
            elif key_name == 'q' :
                print("发出指令大招")
                # click_key('q')
                print("发出指令", 'q')
                pydirectinput.keyDown('q')
                time.sleep(0.5)
                pydirectinput.keyUp('q')
                print("结束指令", 'q')
                time.sleep(1.2)
            elif key_name == 'c' :
                print("发出指令冲刺")
                # click_key('shift')
                print("发出指令", 'shift')
                pydirectinput.keyDown('shift')
                time.sleep(0.3)
                pydirectinput.keyUp('shift')
                print("结束指令", 'shift')
                time.sleep(0.02)
            elif key_name == 'p' :
                print("发出指令停止奔跑")
                print("松开w")
                pydirectinput.keyUp('w')
                print("松开a")
                pydirectinput.keyUp('a')
                print("松开s")
                pydirectinput.keyUp('s')
                print("松开d")
                pydirectinput.keyUp('d')
                print("松开shift")
                pydirectinput.keyUp('shift')
            elif key_name == 'h' :
                print("发出指令普通攻击")
                pyautogui.leftClick()
            elif key_name == 'n' :
                print("发出指令跳跃")
                click_key('space')
    elif key_name in key_list_can_long_press : # skill
        if(last_key_name != key_name) :
            release_key(last_key_name)
            time.sleep(release_sec) # 主要考虑长e一般都有后摇（0.6都不行，最好就是加个n键跳跃取消后摇）
            press_key(key_name)
        else:
            press_key(key_name)
        last_key_name = key_name
    elif key_name in key_list_longger_press : # move
        if(last_key_name != key_name) :
            release_key(last_key_name)
            time.sleep(release_sec) # 主要考虑长e一般都有后摇（0.6都不行，最好就是加个n键跳跃取消后摇）
            press_key(key_name)
            time.sleep(0.5) # press_key函数里默认有press_sec
        else:
            press_key(key_name)
            time.sleep(0.5) # press_key函数里默认有press_sec
        last_key_name = key_name
    elif key_name in key_list:
        release_key(last_key_name)
        click_key(key_name)


        
    # pydirectinput.moveRel(xOffset=-900,yOffset=0,duration=0.4,relative=True)
    # pyautogui.mouseDown()

    # vk_code = 65
    # win32api.keybd_event(vk_code,win32api.MapVirtualKey(vk_code,0),0,0)

    # 获取鼠标位置
    # win32api.GetCursorPos()
    # 将鼠标移动到坐标处
    # win32api.SetCursorPos((200, 200))
    # 左点击
    # win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, 200, 200, 0, 0)
    # win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP, 200, 200, 0, 0)

    # mvka = ctypes.windll.user32.MapVirtualKeyA
    # win32api.keybd_event(0xd, mvka(0xd, 0), 0, 0) # 回车
    # time.sleep(0.2)
    # win32api.keybd_event(0xd, mvka(0xd, 0 ), win32con.KEYEVENTF_KEYUP, 0)
    # mvka = ctypes.windll.user32.MapVirtualKeyA
    # win32api.keybd_event(65, mvka(65, 0), 0, 0) # a
    # time.sleep(0.2)
    # win32api.keybd_event(65, mvka(65, 0 ), win32con.KEYEVENTF_KEYUP, 0)


# 创建并放置两个按钮分别触发两种情况
b1 = tkinter.Button(frame_t, text='开始', width=10, command=start_point, font=('Arial', 10))
b1.pack(side=tkinter.LEFT)
b2 = tkinter.Button(frame_t, text='停止', width=10, command=end_point, font=('Arial', 10))
b2.pack(side=tkinter.LEFT)

# 滚动条
sc = tkinter.Scrollbar(frame_b)
sc.pack(side=tkinter.RIGHT, fill=tkinter.Y)
# Listbox控件
listb = tkinter.Listbox(frame_b, yscrollcommand=sc.set, width=200, height=120)
# 将部件放置到主窗口中
listb.pack(side=tkinter.LEFT, fill=tkinter.BOTH, expand=True)
# 滚动条动，列表跟着动
sc.config(command=listb.yview)

# 主窗口循环显示
window.mainloop()

# if __name__ == '__main__':
    # while True:
    #     control()
